package org.test4j.mock.faking.modifier;

import g_asm.org.objectweb.asm.ClassReader;
import g_asm.org.objectweb.asm.ClassWriter;
import org.test4j.mock.faking.util.AsmConstant;
import org.test4j.mock.faking.util.ClassLoad;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

import static java.lang.reflect.Modifier.*;
import static org.test4j.mock.faking.util.AsmConstant.acceptOptions;
import static org.test4j.mock.faking.util.AsmConstant.writeFlags;
import static org.test4j.mock.faking.util.TypeDesc.T_InvocationHandler;

/**
 * 在加载第一个class loader中定义桥接访问字段
 *
 * @author darui.wu
 */
public class BridgeTransformer implements ClassFileTransformer {
    private static String hostJREClassName;

    private static boolean fieldsSet;

    public BridgeTransformer() {
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if (loader == null && hostJREClassName == null) {
            ClassReader cr = new ClassReader(classfileBuffer);
            if (isPublic(cr.getAccess())) {
                hostJREClassName = className;
                return this.addBridgeField(cr);
            }
        }
        return null;
    }

    /**
     * adds the fields to the first public JRE class to be loaded
     *
     * @param cr
     * @return
     */
    private byte[] addBridgeField(ClassReader cr) {
        ClassWriter cw = new ClassWriter(writeFlags);
        cr.accept(cw, acceptOptions);
        cw.visitField(FIELD_ACCESS, BridgeFakeInvocation.BridgeID, T_InvocationHandler.DESC, null, null).visitEnd();

        cw.visitEnd();
        byte[] bytes = cw.toByteArray();
        return bytes;
    }

    /**
     * 初始化桥接字段
     *
     * @return
     */
    public static String initBridgeField() {
        if (fieldsSet || hostJREClassName == null) {
            return hostJREClassName;
        }
        Class hostClass = ClassLoad.loadClass(hostJREClassName);
        try {
            hostClass.getDeclaredField(BridgeFakeInvocation.BridgeID).set(null, BridgeFakeInvocation.INSTANCE);
        } catch (NoSuchFieldException ignore) {
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        fieldsSet = true;
        return hostJREClassName;
    }

    private static final int FIELD_ACCESS = PUBLIC + STATIC + AsmConstant.SYNTHETIC;
}